"""
输入验证中间件
用于防止 SQL 注入、XSS 攻击等安全问题
"""
import re
import os
from typing import Optional
from fastapi import HTTPException
import logging

logger = logging.getLogger(__name__)


class InputValidator:
    """输入验证器 - 防止各种注入攻击"""
    
    # SQL 注入危险模式
    SQL_INJECTION_PATTERNS = [
        r"(\bDROP\b|\bDELETE\b|\bTRUNCATE\b).*\bTABLE\b",
        r"\bUPDATE\b.*\bSET\b",
        r"\bINSERT\b.*\bINTO\b",
        r"--",
        r"\/\*.*\*\/",
        r"\bOR\b\s+\d+\s*=\s*\d+",
        r"\bAND\b\s+\d+\s*=\s*\d+",
        r";\s*(DROP|DELETE|UPDATE|INSERT)",
        r"UNION.*SELECT",
        r"exec\s*\(",
        r"script\s*>",
    ]
    
    # XSS 危险模式
    XSS_PATTERNS = [
        r"<script[^>]*>.*?</script>",
        r"javascript:",
        r"onerror\s*=",
        r"onload\s*=",
        r"onclick\s*=",
        r"<iframe",
        r"<embed",
        r"<object",
    ]
    
    # 允许的文件类型及其扩展名
    ALLOWED_FILE_TYPES = {
        'application/pdf': ['.pdf'],
        'application/vnd.openxmlformats-officedocument.wordprocessingml.document': ['.docx'],
        'application/msword': ['.doc'],
        'application/vnd.openxmlformats-officedocument.presentationml.presentation': ['.pptx'],
        'application/vnd.ms-powerpoint': ['.ppt'],
        'text/plain': ['.txt'],
        'image/jpeg': ['.jpg', '.jpeg'],
        'image/png': ['.png'],
        'image/gif': ['.gif'],
        'image/webp': ['.webp'],
    }
    
    @classmethod
    def sanitize_string(
        cls,
        text: str,
        max_length: int = 1000,
        allow_html: bool = False,
        field_name: str = "输入"
    ) -> str:
        """
        清理和验证字符串输入
        
        Args:
            text: 要验证的文本
            max_length: 最大长度限制
            allow_html: 是否允许 HTML（用于富文本编辑器）
            field_name: 字段名称（用于错误提示）
        
        Returns:
            清理后的文本
        
        Raises:
            HTTPException: 如果输入不合法
        """
        if text is None:
            return ""
        
        if not isinstance(text, str):
            raise HTTPException(400, f"{field_name}必须是字符串")
        
        # 去除首尾空格
        text = text.strip()
        
        # 检查长度
        if len(text) > max_length:
            raise HTTPException(400, f"{field_name}过长，最多 {max_length} 字符")
        
        # 检测 SQL 注入
        for pattern in cls.SQL_INJECTION_PATTERNS:
            if re.search(pattern, text, re.IGNORECASE):
                logger.warning(f"检测到疑似 SQL 注入攻击: {text[:100]}")
                raise HTTPException(400, f"{field_name}包含非法字符")
        
        # 检测 XSS 攻击
        if not allow_html:
            for pattern in cls.XSS_PATTERNS:
                if re.search(pattern, text, re.IGNORECASE):
                    logger.warning(f"检测到疑似 XSS 攻击: {text[:100]}")
                    raise HTTPException(400, f"{field_name}包含非法字符")
        
        return text
    
    @classmethod
    def validate_email(cls, email: str) -> str:
        """
        验证邮箱格式
        
        Args:
            email: 邮箱地址
        
        Returns:
            规范化的邮箱地址
        
        Raises:
            HTTPException: 如果邮箱格式不正确
        """
        if not email:
            raise HTTPException(400, "邮箱不能为空")
        
        email = email.strip().lower()
        
        # 简单的邮箱格式验证
        email_pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
        if not re.match(email_pattern, email):
            raise HTTPException(400, "邮箱格式不正确")
        
        if len(email) > 255:
            raise HTTPException(400, "邮箱地址过长")
        
        return email
    
    @classmethod
    def validate_username(cls, username: str) -> str:
        """
        验证用户名
        
        Args:
            username: 用户名
        
        Returns:
            验证后的用户名
        
        Raises:
            HTTPException: 如果用户名不合法
        """
        if not username:
            raise HTTPException(400, "用户名不能为空")
        
        username = username.strip()
        
        # 用户名长度限制
        if len(username) < 3:
            raise HTTPException(400, "用户名至少 3 个字符")
        if len(username) > 50:
            raise HTTPException(400, "用户名最多 50 个字符")
        
        # 只允许字母、数字、下划线、中文
        if not re.match(r'^[\w\u4e00-\u9fa5]+$', username):
            raise HTTPException(400, "用户名只能包含字母、数字、下划线或中文")
        
        return username
    
    @classmethod
    def validate_password(cls, password: str) -> str:
        """
        验证密码强度
        
        Args:
            password: 密码
        
        Returns:
            验证后的密码
        
        Raises:
            HTTPException: 如果密码不符合要求
        """
        if not password:
            raise HTTPException(400, "密码不能为空")
        
        # 密码长度限制
        if len(password) < 6:
            raise HTTPException(400, "密码至少 6 个字符")
        if len(password) > 128:
            raise HTTPException(400, "密码最多 128 个字符")
        
        # 可选：强密码策略（生产环境建议启用）
        # has_upper = any(c.isupper() for c in password)
        # has_lower = any(c.islower() for c in password)
        # has_digit = any(c.isdigit() for c in password)
        # if not (has_upper and has_lower and has_digit):
        #     raise HTTPException(400, "密码必须包含大小写字母和数字")
        
        return password
    
    @classmethod
    def validate_file_upload(
        cls,
        filename: str,
        content_type: str,
        file_size: Optional[int] = None,
        max_size: int = 100 * 1024 * 1024  # 100MB
    ) -> bool:
        """
        验证文件上传
        
        Args:
            filename: 文件名
            content_type: 文件 MIME 类型
            file_size: 文件大小（字节）
            max_size: 最大文件大小（字节）
        
        Returns:
            True 如果验证通过
        
        Raises:
            HTTPException: 如果文件不符合要求
        """
        if not filename:
            raise HTTPException(400, "文件名不能为空")
        
        # 检测危险文件名（路径遍历攻击）
        if '..' in filename or '/' in filename or '\\' in filename:
            logger.warning(f"检测到疑似路径遍历攻击: {filename}")
            raise HTTPException(400, "文件名包含非法字符")
        
        # 检查文件名长度
        if len(filename) > 255:
            raise HTTPException(400, "文件名过长")
        
        # 验证文件类型
        if content_type not in cls.ALLOWED_FILE_TYPES:
            raise HTTPException(
                400,
                f"不支持的文件类型: {content_type}。"
                f"支持的类型: PDF, Word, PowerPoint, TXT, 图片"
            )
        
        # 验证文件扩展名
        ext = os.path.splitext(filename)[1].lower()
        allowed_exts = cls.ALLOWED_FILE_TYPES[content_type]
        if ext not in allowed_exts:
            raise HTTPException(
                400,
                f"文件扩展名 {ext} 与类型 {content_type} 不匹配"
            )
        
        # 验证文件大小
        if file_size is not None:
            if file_size <= 0:
                raise HTTPException(400, "文件大小无效")
            if file_size > max_size:
                max_mb = max_size / (1024 * 1024)
                raise HTTPException(400, f"文件过大，最大 {max_mb:.0f}MB")
        
        return True
    
    @classmethod
    def validate_id(cls, id_str: str, field_name: str = "ID") -> str:
        """
        验证 UUID 格式的 ID
        
        Args:
            id_str: ID 字符串
            field_name: 字段名称
        
        Returns:
            验证后的 ID
        
        Raises:
            HTTPException: 如果 ID 格式不正确
        """
        if not id_str:
            raise HTTPException(400, f"{field_name}不能为空")
        
        # UUID 格式验证
        uuid_pattern = r'^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$'
        if not re.match(uuid_pattern, id_str.lower()):
            raise HTTPException(400, f"{field_name}格式不正确")
        
        return id_str
    
    @classmethod
    def sanitize_search_query(cls, query: str, max_length: int = 200) -> str:
        """
        清理搜索查询
        
        Args:
            query: 搜索查询
            max_length: 最大长度
        
        Returns:
            清理后的查询
        """
        if not query:
            return ""
        
        # 清理搜索查询
        query = cls.sanitize_string(query, max_length=max_length, field_name="搜索查询")
        
        # 移除特殊通配符（避免性能问题）
        query = query.replace('%', '').replace('_', '')
        
        return query

